package p2pcdnsim.cdn;

import p2pcdnsim.exception.PortAlreadyInUseException;
import p2pcdnsim.network.ApplicationProcess;
import p2pcdnsim.network.DatagramPacket;
import p2pcdnsim.network.DatagramSocket;
import p2pcdnsim.report.SimReporter;
import desmoj.core.simulator.Model;
import desmoj.core.simulator.SimTime;

public class VoDSurrogate extends Surrogate {

	protected CachingStrategy<VideoContent> cache;
	
	public CachingStrategy<VideoContent> getCache() {
		return cache;
	}

	public void setCache(CachingStrategy<VideoContent> cache) {
		this.cache = cache;
	}

	public VoDSurrogate(Model owner, String name, CDN cdn, CachingStrategy<VideoContent> cache) {
		super(owner, name, cdn);
		this.cache = cache;
	}

	@Override
	protected void run() {
		DatagramSocket socket = null;
		try {
			socket = this.createDatagramSocket(80);
		} catch (PortAlreadyInUseException e) {
			throw new RuntimeException(e);
		}
		
		while(true) {
			//System.out.println("["+this.currentTime()+"] VoDSurrogate: Waiting for a datagram...");
			DatagramPacket packet = socket.receive();
			SimReporter.getInstance().invokeReporter(SimReporter.NUMBER_OF_REQUESTS, "update");
			SurrogateRoutine routine =  new SurrogateRoutine(this, packet);
			routine.activate(new SimTime(0.0));	
		}
	}

	
	
	private class SurrogateRoutine extends ApplicationProcess {

		DatagramPacket packet;
		
		public SurrogateRoutine(VoDSurrogate parent, DatagramPacket packet) {
			super(parent.getModel(), parent.getName());
			this.setApplicationLayer(parent.getApplicationLayer());
			this.packet = packet;
		}
		
		@Override
		protected void run() {
			DatagramSocket routineSocket = createDatagramSocket();
			
			//System.out.println("["+this.currentTime()+"] VoDSurrogate subroutine: received request");

			ObjectRequest request = (ObjectRequest)packet.getMessage();
			//System.out.println("["+this.currentTime()+"] VoDSurrogate subroutine: requesting object "+request.getObjectId());
			
			VideoContent video = cache.retrieveObject(request.getObjectId(), this.currentTime());
			if(video == null) {
				System.out.println("["+this.currentTime()+"] VoDSurrogate subroutine: cache miss.");
				SimReporter.getInstance().invokeReporter(SimReporter.MISS_RATIO, "update");
				
				//Send request to origin server
				DatagramPacket newpacket = new DatagramPacket(routineSocket.getAddress(),routineSocket.getPort(),cdn.getOriginServerAddress(),80);
				newpacket.setMessage(new ObjectRequest(request.getObjectId()));
				routineSocket.send(newpacket);
				
				//Send it to the client
				long size = 0;
				VideoContent fragment;
				do {
					fragment = (VideoContent)routineSocket.receive().getMessage();
					size+=fragment.getLength();
					//System.out.println("RESPONSE RECEIVED "+fragment);
					newpacket = new DatagramPacket(routineSocket.getAddress(),routineSocket.getPort(),packet.getSourceAddress(),packet.getSourcedPort());
					newpacket.setMessage(fragment);
					routineSocket.send(newpacket);
				} while(!fragment.isLastFragment());
				//System.out.println(size);
				
				//insert into cache
				cache.insertObject(new VideoContent(fragment.getObjectId(),fragment.getTotalSize(),fragment.getBitRate()), this.currentTime());
			} else {
				SimReporter.getInstance().invokeReporter(SimReporter.HIT_RATIO, "update");
				
				//Fragmenting...
				long lenRemaining = video.getLength();
				int packetSize = 1400;
				while(lenRemaining>0) {
					VideoContent videoFragment;
					if(lenRemaining > packetSize) {
						videoFragment = new VideoContent(video.getObjectId(),packetSize,video.getBitRate());
						lenRemaining-=packetSize;
					} else {
						videoFragment = new VideoContent(video.getObjectId(),lenRemaining,video.getBitRate());
						videoFragment.setLastFragment(true);
						lenRemaining=0;
					}
					videoFragment.setFragment(true);
					videoFragment.setTotalSize(video.getLength());
					DatagramPacket newpacket = new DatagramPacket(routineSocket.getAddress(),routineSocket.getPort(),packet.getSourceAddress(),packet.getSourcedPort());
					newpacket.setMessage(videoFragment);
					routineSocket.send(newpacket);
					SimTime time = new SimTime(1/(((double)video.getBitRate())/(packetSize*8)));
					
					if(this.isScheduled()) throw new RuntimeException("trying to hold but already scheduled at "+this.scheduledAt());
					hold(time);
				}
			}
			routineSocket.close();
		}
	}
}
